﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using TrainGame.Model;
using TrainGame.Engine;
using TrainGame.Engine.RouteCalc;

namespace TrainGame.AI.TileLay
{
	/// <summary>
	/// Improve routes for current trains
	/// </summary>
	internal class ImproveCurrentRoutesTileGoal : TileLayGoal
	{
		public override string GetName(PublicCompany company)
		{
			return "ImproveCurrentRoutes";
		}

		/// <summary>
		/// Hexes where too-short routes terminate.
		/// This is cached by ComputeNeed and used by ComputeTileLays
		/// </summary>
		private List<string> TooShortEndpoints;

		/// <summary>
		/// how much do we need to improve current routes?
		/// </summary>
		protected override int ComputeNeed(Gameboard gameboard, PublicCompany company, Dictionary<string, GameboardHex> runToHexes, AiMoveConfig config)
		{
			TooShortEndpoints = new List<string>();

			// If no trains, nothing to do here (another goal will handle it)
			if (company.Trains.Count == 0)
				return 0;

			bool desperate;	// not used
			return CalculateNeedForTrains(gameboard, company, company.Trains, company.Game.Tiles, config, TooShortEndpoints, out desperate);
		}

		static public int CalculateNeedForTrains(Gameboard gameboard, PublicCompany company, TrainCollection trains,
			Tiles tiles, AiMoveConfig config, List<string> tooShortEndpoints, out bool desperate)
		{
			RouteCollection routes = RouteCalculator.FindBestRoutes(gameboard, company, trains, false);

			// If not enough routes for trains, that's a big need
			if (routes.Count < trains.Count)
			{
				desperate = true;
				foreach (Route route in routes)
					AddAllRouteEndpoints(tooShortEndpoints, route);
				return 50;
			}

			// Each too-short route for a train adds to the need
			desperate = false;
			int perStop = 10;
			if (config.AllowBrownTile)
				perStop += 30;
			else if (config.AllowGreenTile)
				perStop += 15;
			TrainCollection tempTrains = new TrainCollection(trains);
			tempTrains.SortHighestToLowest();
			int need = 0;
			for (int i = 0; i < tempTrains.Count; i++)
				if (routes[i].Stops < tempTrains[i].Stops)
				{
					need += perStop * (tempTrains[i].Stops - routes[i].Stops);
					AddTooShortEndpoint(tooShortEndpoints, routes[i].TrackSegments[0].Start);
					AddTooShortEndpoint(tooShortEndpoints, routes[i].TrackSegments[routes[i].TrackSegments.Length - 1].End);
				}

			// no short routes, are there any obvious city upgrades?
			if ((need == 0) && config.AllowGreenTile)
			{
				foreach (Route route in routes)
				{
					for (int t=0; t < route.TrackSegments.Length; t++)
					{
						TrackSegment track = route.TrackSegments[t];
						// look at start city only at first city, rest should only look at end city to not double count them
						if (t == 0)
							need += NeedForCity(track.Start, tiles, config);
						else
							need += NeedForCity(track.End, tiles, config);
					}
				}
			}
			//if (need == 0)
			//	need = 2;	// always a dribble of need, since the 'obvious' upgrades needs can miss stuff
			return need;
		}

		static private void AddAllRouteEndpoints(List<string> tooShortEndpoints, Route route)
		{
			if (route.TrackSegments.Length > 0)
			{
				AddTooShortEndpoint(tooShortEndpoints, route.TrackSegments[0].Start);
				for (int i=1; i < route.TrackSegments.Length; i++)
					AddTooShortEndpoint(tooShortEndpoints, route.TrackSegments[i].End);
			}
		}

		static private void AddTooShortEndpoint(List<string> tooShortEndpoints, SegmentEndpoint endpoint)
		{
			// ignore offboard (NOTE: ASSUMES NO RUN-THROUGH OFFBOARD HEXES)
			if (endpoint.AtCity && (! endpoint.City.Offboard))
			{
				string loc = endpoint.City.Hex.Location;
				if (!tooShortEndpoints.Contains(loc))
					tooShortEndpoints.Add(loc);
			}
		}

		static private int NeedForCity(SegmentEndpoint endpoint, Tiles tiles, AiMoveConfig config)
		{
			if (! endpoint.AtCity)
				return 0;

			// ignore offboard, they aren't upgradeable
			// TODO: ignoring offboard means we could also end up ignoring an upgradeable board city that this route happens to not hit (because same value route)
			if (endpoint.City.Offboard)
				return 0;

			// ignore towns unless they are upgradable (phase 6, grays and town-to-city upgrades available)
			if (endpoint.AtTown && (!config.AllowGrayTile))
				return 0;

			return Math.Max(0, AiUtils.GetMaxCityValue(endpoint.City, tiles, config) - endpoint.City.Value);
		}

		/// <summary>
		/// Find the best move or moves.
		/// 
		/// Based on too-short routes discovered during ComputeNeed, we'll add a little juice to tile lays that
		/// build new routes from the endpoints of too-short routes.
		/// 
		/// If no tile lay improves our current routes, we'll default to the ImproveRoutesForPLannedTrains goal,
		/// since it uses a longer-term look-ahead algorithm
		/// </summary>
		internal override List<AiTileLay> ComputeTileLays(Stopwatch watch, TimeSpan timeLimit, Gameboard gameboard, PublicCompany company, List<AiTileLay> tileLays, Dictionary<string, GameboardHex> runToHexes, Dictionary<string, GameboardHex> tileLayHexes, AiMoveConfig config)
		{
			List<AiTileLay> bestTileLays = null;
			int bestValue = -1;
			int currentRouteValue = RouteCalculator.FindBestRoutes(gameboard, company, company.Trains, false).TotalValue;

			// If token AI will lay a token, take it into account now
			GameboardHex tokenHex;
			if (AiWillPlayToken(out tokenHex))
				ApplyToken(tokenHex);

			foreach (AiTileLay tileLay in tileLays)
			{
				if (watch.Elapsed.CompareTo(timeLimit) > 0)
					goto done;	// time limit expired, return the best tile lay so far

				gameboard.ApplyTileLayWithUndo(tileLay);	// add tile lay to game board
				tileLay.TransferHexNewRoutes(company, gameboard);

				RouteCollection routes = RouteCalculator.FindBestRoutes(gameboard, company, company.Trains, false);

				tileLay.GoalValue = routes.TotalValue;	// do this before undo-ing the tile lay!

				// HACK: double-town tile lays with no upgrade/downgrade path are worse than all others
				if ((tileLay.Tile.TownType == GameboardHex.eTownType.DblTown) &&
				  (tileLay.Tile.ValidUpgrades.Count == 0) && (tileLay.Tile.ValidDowngrades.Count == 0))
					tileLay.GoalValue--;

				// HACK: upgading a city where we have a token is better than a city without our token
				if ((tileLay.Tile.TownType == GameboardHex.eTownType.City) && (company.TokenHexes[tileLay.Hex.Location] != null))
					tileLay.GoalValue++;

				gameboard.UndoTileLay(tileLay);	// restore the game board

				if (tileLay.GoalValue == bestValue)
					bestTileLays.Add(tileLay);
				else if (tileLay.GoalValue > bestValue)
				{
					bestTileLays = new List<AiTileLay>();
					bestTileLays.Add(tileLay);
					bestValue = tileLay.GoalValue;
				}
			}

		done:
			if (bestValue > currentRouteValue)
				return bestTileLays;

			// TODO: give priority to tile lays that connect to an endpoint of a too-short route 

			// If no tile lay improves our current routes, let the picker choose the next-highest-need goal
			return null;
		}

		// TODO: move this into token AI
		static public bool AiWillPlayToken (out GameboardHex tokenHex)
		{
			tokenHex = null;
			return false;	// TEMP
		}

		// TODO: move this into game engine or token AI
		static public void ApplyToken(GameboardHex tokenHex)
		{
			throw new NotImplementedException();
		}
	}
}
